-module(environment).
%-compile([native]).
-export([
    new/0,
    new/1,
    assign_var/3,
    create_var/3,
    get_var/2,
    new_scope/1,
    strip_scope/1,
    push_output/2,
    get_output/1,
    get_input/1,
    handle_output/1,
    test/0
    ]).
-include("../include/environment.hrl").

new() -> new(undefined).
new(Collector) -> #env{vars=[[]], funcs=[], it={noob, noob},
    out_collector=Collector}.

get_input(#env{out_collector=undefined}=_Environment) ->
	io:get_line('');
get_input(#env{out_collector=Collector}=_Environment) when is_pid(Collector) ->
	Collector ! get_input,
	receive
		{get_input, String} when is_list(String) -> String
	after 1000 ->
		eof
	end.

push_output(#env{out_collector=undefined}=Environment, String) when is_list(String) ->
    Pid=spawn_link(environment, handle_output, [[]]),
    push_output(Environment#env{out_collector=Pid}, String);
push_output(#env{out_collector=Collector}=Environment, String) when is_list(String) ->
    Collector ! {push_string, String},
    Environment.

get_output(#env{out_collector=undefined}) -> [];
get_output(#env{out_collector=Collector}) ->
    Collector ! {get_output, self()},
    receive
		Strings when is_list(Strings) -> Strings
    after 1000 ->
		[]
    end.

handle_output(Strings) when is_list(Strings) ->
    receive
        {push_string, String} -> handle_output(Strings ++ String);
        {'EXIT', _FromPid, _Reason} -> ok;
        {get_output, FromPid} ->
            FromPid ! Strings,
            handle_output([])
    end.

set_var_block(Browsed, [], _Var, _Val) when is_list(Browsed) ->
    {not_found, Browsed};
set_var_block(Browsed, [{Name,_} | Vars], Var, NewVal) when is_list(Browsed),Name==Var -> {ok, Browsed ++ [{Name, NewVal}] ++ Vars};
set_var_block(Browsed, [OldVar | Vars], Var, NewVal) when is_list(Browsed) -> set_var_block(Browsed ++ [OldVar], Vars, Var, NewVal).

set_var_multiblock(Browsed, [], _, _) when is_list(Browsed) ->
    {not_found, Browsed};
set_var_multiblock(Browsed, [Current|Others], Var, Val) when is_list(Browsed),is_list(Current) ->
    case set_var_block([], Current, Var, Val) of
        {not_found, _} -> set_var_multiblock(Browsed ++ [Current], Others, Var, Val);
        {ok, NewCurrent} -> Browsed ++ [NewCurrent] ++ Others
    end.

create_var_multiblock([Current|Others], Var, Val) ->
    case set_var_block([], Current, Var, Val) of
        {not_found, _} -> [Current ++ [{Var, Val}]] ++ Others;
        {ok, NewCurrent} -> [NewCurrent] ++ Others
    end.
create_var(Environment, "IT", Val) when is_record(Environment, env) ->
    Environment#env{it = Val};
create_var(Environment, Var, Val) when is_record(Environment, env)->
    Environment#env{vars=create_var_multiblock(Environment#env.vars, Var, Val)}.

assign_var(Environment, {identifier, "IT"}, Val) when is_record(Environment, env) ->
    Environment#env{it = Val};
assign_var(Environment, {identifier, Var}, Val) when is_record(Environment, env) ->
    Environment#env{
        vars = set_var_multiblock([], Environment#env.vars, Var, Val)};
assign_var(Environment, {bukkit_selector, {identifier, Var}, Key}, Val) when is_record(Environment, env),is_list(Key) ->
    NewBukkit = case get_var(Environment, Var) of
        {noob,noob} -> vars:bucket_new();
        {bukkit,_}=Bukkit -> Bukkit
    end,
    EKey = lists:map(fun(A) -> runner:evaluate(Environment, A) end, Key),
    assign_var(Environment, {identifier, Var}, vars:bucket_add(NewBukkit, EKey, Val)).

get_var_block(_Browsed, [], _Var) -> not_found;
get_var_block(_Browsed, [{Name, Tup}|_], Var) when Name==Var -> Tup;
get_var_block(Browsed, [Current|Other], Var) -> get_var_block(Browsed ++ [Current], Other, Var).

get_var_multiblock(_Browsed, [], _Var) -> not_found;
get_var_multiblock(Browsed, [Current|Other], Var) ->
    case get_var_block([], Current, Var) of
        not_found -> get_var_multiblock(Browsed ++ [Current], Other, Var);
        {_Type, _Val}=Val -> Val
    end.

get_var(Environment, "IT") when is_record(Environment, env) ->
    Environment#env.it;
get_var(Environment, Var) when is_record(Environment, env) ->
    case get_var_multiblock([], Environment#env.vars, Var) of
        not_found -> {noob,noob};
        {_,_}=Val -> Val
    end.

new_scope(Environment) when is_record(Environment, env) ->
    Environment#env{vars = [[]] ++ Environment#env.vars}.

strip_scope(#env{vars=[_Stripped|Left]}=Environment) when is_record(Environment, env) ->
    Environment#env{vars = Left}.

%%%%%%%%%

test_set() -> [
    {fun({Vars, Var, Val}) -> set_var_block([], Vars, Var, Val) end,
        {[{"klm", {yarn, "def"}}, {"abc", {yarn, "def"}}, {"pqr", {yarn, "def"}}], "abc", {yarn, "ghi"}},
        {ok, [{"klm", {yarn, "def"}}, {"abc", {yarn, "ghi"}}, {"pqr", {yarn, "def"}}]}},
    {fun({Vars, Var, Val}) -> set_var_block([], Vars, Var, Val) end,
        {
            [{"abc", {yarn, "def"}}],
            "xyz",
            {yarn, "ghi"}
        },
        {not_found, [{"abc", {yarn, "def"}}]}},
    {fun({Env, Var, Val}) -> create_var(Env, Var, Val) end,
        {
            new(),
            "abc",
            {yarn, "ghi"}
        },
        #env{vars=[[{"abc", {yarn, "ghi"}}]], funcs=[], it={noob,noob}}},
    {fun(A) -> new_scope(A) end,
        new(),
        (new())#env{vars=[[],[]]}},
    {fun(Env) -> strip_scope(Env) end,
        (new())#env{vars=[[],[]]},
        new()},
    {fun({Env, Var, Val}) -> create_var(Env, Var, Val) end,
        {
            create_var(new_scope(create_var(new(), "abc", {yarn, "def"})), "abc", {yarn, "xyz"}),
            "pqr",
            {noob, noob}
        },
        (new())#env{
            vars=[[{"abc", {yarn, "xyz"}}, {"pqr", {noob, noob}}], [{"abc", {yarn, "def"}}]]
            }},
    {fun({Env, Var, Val}) -> assign_var(Env, Var, Val) end,
        {
            (new())#env{vars=[[{"abc", {yarn, "def"}}], [{"ghi", {yarn, "jkl"}}]]},
            {identifier, "abc"},
            {noob, noob}
        },
        (new())#env{
            vars=[[{"abc", {noob, noob}}], [{"ghi", {yarn, "jkl"}}]]
            }},
    {fun({Env, Var, Val}) -> assign_var(Env, Var, Val) end,
        {
            (new())#env{vars=[[{"abc", {yarn, "def"}}], [{"ghi", {yarn, "jkl"}}]]},
            {identifier, "ghi"},
            {noob, noob}
        },
        (new())#env{vars=[[{"abc", {yarn, "def"}}], [{"ghi", {noob, noob}}]]}},
    {fun({Env, Var}) -> get_var(Env, Var) end,
        {
            (new())#env{vars=[[{"abc", {yarn, "def"}}], [{"ghi", {yarn, "jkl"}}]]},
            "abc"
        },
        {yarn, "def"}},
    {fun({Env, Var}) -> get_var(Env, Var) end,
        {
            (new())#env{vars=[[{"abc", {yarn, "def"}}], [{"ghi", {yarn, "jkl"}}]]},
            "ghi"
        },
        {yarn, "jkl"}},
    {fun({Env, Var}) -> get_var(Env, Var) end,
        {
            (new())#env{vars=[[{"abc", {yarn, "def"}}], [{"ghi", {yarn, "jkl"}}]]},
            "xyz"
        },
        {noob,noob}},
    {fun({Env, Var}) -> get_var(Env, Var) end,
        {
            (new())#env{it={yarn,"def"}},
            "IT"
        },
        {yarn, "def"}},
    {fun({Env, Var, Val}) -> assign_var(Env, Var, Val) end,
        {
            (new())#env{vars=[[{"abc", vars:bucket_new()}]]},
            {bukkit_selector, {identifier, "abc"}, [{numbr,1}]},
            {troof, win}
        },
        (new())#env{
            vars=[[{"abc", vars:bucket_add(vars:bucket_new(), [{numbr,1}], {troof,win})}]]
            }},
    {fun(Env) -> NewEnv=push_output(Env, "abc"), get_output(NewEnv) end,
        new(),
        "abc"},
    {fun(Env) -> NewEnv=push_output(push_output(Env, "abc"), "def"), get_output(NewEnv) end,
        new(),
        "abcdef"}
    ].

test() -> tester:test(test_set()).

